/*===========================================================================*\
|
|  File:        cgchar.cpp
|
|  Description: 
|       
|-----------------------------------------------------------------------------
|
|  Copyright (C) 1995-1996 Microsoft Corporation.  All Rights Reserved.
|
|  Written by Moss Bay Engineering, Inc. under contract to Microsoft Corporation
|
\*===========================================================================*/

/**************************************************************************

    (C) Copyright 1995-1996 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and 
    distribute the Sample Files (and/or any modified version) in 
    any way you find useful, provided that you agree that 
    Microsoft has no warranty obligations or liability for any 
    Sample Application Files which are modified. 

    we do not recomend you base your game on IKlowns, start with one of
    the other simpler sample apps in the GDK

 **************************************************************************/

#include <windows.h>
#ifdef __WATCOMC__
#include <mem.h>
#else
#include <memory.h>
#endif
#include "cgdebug.h"
#include "cgglobl.h"
#include "cgtimer.h"
#include "cginput.h"
#include "cgchar.h"
#include "cgchdll.h"
#include "cgchrint.h"
#include "cgload.h"
#include "cgremote.h"
#include "strrec.h"

extern void dbgprintf(char *, ...);
extern CLoadingScreen *gLoadingScreen;

static int DefaultCharacterAction (CGameCharacter *me, CGameLevel *level)
{
    // move to next sprite sequence...
    me->NextSprite(level->GetFrameTime());

    // always stays in state 0 ...
    return(0);
}

static int DefaultCharacterCollide (CGameCharacter *me, CGameCharacter *other, CGameLevel *level)
{
    // what to do if we collided????
    return(ACTION_COMPLETED);
}

/*---------------------------------------------------------------------------*\
|
|       Class CGameCharacter
|
|  DESCRIPTION:
|
|
|
\*---------------------------------------------------------------------------*/
CGameCharacter::CGameCharacter(
    char* pFileName,
    char* pCharName,
    char* pGraphicKey,
    CGameLevel *pLevel,
    int minz,
    int maxz,
    int startx,
    int starty,
    void *pNewObjID,
    char *pRemoteName
    ) : CGameGraphic( minz ),
        mpActions( NULL ),
        mpName( NULL ),
        mNumActions( 0 ),
        mpCurAction( NULL ),
        mCurSubX( 0 ),
        mCurSubY( 0 ),
        mVelocityX( 0 ),
        mVelocityY( 0 ),
        mLastTime( -1 )
{
    static DWORD gObjInstance=0;
    // hook into the character DLL (or built-in) functionality, if present
    // note: if nothing is found, then we'll use *default* actions!
    mpLevel = pLevel;
    mpName = new char[lstrlen( pCharName ) + 1];
    lstrcpy( mpName, pCharName );

    mpCharInfo = FindCharInfo(pCharName);
    if (mpCharInfo)
    {
        mpCharInfo->Create(this, pLevel);
        pCollideFunc = mpCharInfo->Collide;
        mNumActions = mpCharInfo->NumSequences;
    }
    else
    {
        pActFunc = DefaultCharacterAction;
        pCollideFunc = DefaultCharacterCollide;
        mNumActions = 1;
    }

    // now that we know how many actions there are, allocate our array 
    mpActions = new CGameAction *[mNumActions];

    char    *actionBuf;
    char    defAction[] = "";       // no default action names

    if (mpCharInfo)
    {
        int ix = 0;
        for ( ix = 0; ix < mNumActions ; ix++ ) 
        {
            if (gLoadingScreen != NULL)
                gLoadingScreen->Update();

            mpActions[ix] = new CGameAction( pFileName, &(mpCharInfo->Sequences[ix])
            , (DWORD)this );
        }
    }
    else
    {
        actionBuf = new char [32 * mNumActions];
        GetPrivateProfileString( 
            pCharName,
            NULL,       // grab all the key names
            defAction,
            actionBuf,
            32 * mNumActions,
            pFileName
             );

        int ix = 0;
        for ( char *pAction = actionBuf; *pAction && ( ix < mNumActions ); pAction++, ix++ ) {
            if (gLoadingScreen != NULL)
                gLoadingScreen->Update();
            mpActions[ix] = new CGameAction( pFileName, pAction
            , (DWORD)this );
            pAction += lstrlen( pAction );  // move beyond terminator
        }

        delete [] actionBuf;
    }

    // Initialize remote stuff
    pObjID = (REMOTE_OBJECT *)pNewObjID;
    pActionQueue = NULL;
    fRemote = FALSE;
    if (mpCharInfo)
    {
        if (pObjID != NULL)
        {
            fRemote = TRUE;
            pActFunc = mpCharInfo->RemoteAction;
            
            // Create an action queue
            pActionQueue = CreateRemoteObjectQueue(pObjID, (void *)this);

        } else {
            if (mpCharInfo->RemoteAction != NULL)
            {
                if (pRemoteName == NULL)
                    pRemoteName = pCharName;

                pObjID = CreateRemotePeers(pRemoteName, gObjInstance++);
            }
            pActFunc = mpCharInfo->Action;
        }
    }

    mMaxZ = maxz;
    mMinZ = minz;
    mCurZ = (mMaxZ+mMinZ)/2;

    mpCurAction = mpActions[0];
    curState = -1;
    MoveTo(startx, starty);
}

CGameCharacter::~CGameCharacter()
{
    if (fRemote)
    {
        // Get rid of the action queue
        DestroyRemoteObjectQueue(pObjID);
    }
    else 
    {
            DestroyRemotePeer(pObjID);
    }

    // delete all the action objects
    for ( ; mNumActions > 0; mNumActions-- ) 
    {
        delete  mpActions[mNumActions - 1];
    }

    delete[] mpActions;
    delete[] mpName;
}

void CGameCharacter::Update(
    CGameLevel* pLevel,
    CGameUpdateList* pList      // we add to this if we move
    )
{
    if (mpNext)
        mpNext->Update(pLevel, pList);

    CGameAction* mpPrevAction = mpCurAction;

    int newState = (pActFunc)(this, pLevel);

    // set new state
    if (newState != -1)
    {
        // don't change state if out of bounds!
        if (newState >= mNumActions)
        {
            newState = curState;
        }

        if (newState >= 0)
            mpCurAction = mpActions[newState];
    }

    // See if this is a new action we've entered into.
    if (newState != curState)
    {
        mpPrevAction->DeActivate();
        curState = newState;
        if (curState != -1 && curState != -2)
        {
            mpCurAction->Activate();
        }
    }

    //if (mpCurAction != NULL)
    mpCurAction->Update(pLevel->World2ScreenX(SUB2WORLD(mCurSubX), mXParallax));

    if (newState == -1)
    {
        // kill me
        Destroy(this, pLevel);
        pLevel->Remove(this);
    }
}

// display the character in its position on given screen with given screen offset
void
CGameCharacter::Render(
    CGameLevel* pLevel,
    CGameScreen* pScreen,
    CGameUpdateList* pList
    )
{
    if (mpNext)
    {
        // recurse down the list to get background filled in
        mpNext->Render(pLevel, pScreen, pList);
    }

    RECT update;

    // only draw if we're in the invalid list:
    if (IntersectRect( &update, pList->GetDirtyRect(), &mRect))
    {
        mpCurAction->Render(
                pScreen,
                pLevel->World2ScreenX(SUB2WORLD(mCurSubX), mXParallax),
                pLevel->World2ScreenY(SUB2WORLD(mCurSubY), mYParallax),
                FALSE,  // don't ever reverse the image
                FALSE,
                pList->GetDirtyRect()
                );
    }
}   

void
CGameCharacter::MoveTo( int worldX, int worldY )
{
    mpLevel->AddInvalidRect(&mRect);

    mCurSubX = WORLD2SUB(worldX);
    mCurSubY = WORLD2SUB(worldY);

    mRect.left = mpLevel->World2ScreenX(worldX, mXParallax);
    mRect.top = mpLevel->World2ScreenY(worldY, mYParallax);
    mRect.right = mRect.left + GetCurWidth() - 1;
    mRect.bottom = mRect.top + GetCurHeight() - 1;

    mpLevel->AddInvalidRect(&mRect);
}

int
CGameCharacter::NextSprite(
    int time,   // current game time
    BOOL wrap   // should sequence wrap around?
    )
{
    mpLevel->AddInvalidRect(&mRect);
    int result = mpCurAction->NextSprite(time, wrap);

    // need to recalculate screen position in case screen moved
    mRect.left = mpLevel->World2ScreenX(SUB2WORLD(mCurSubX), mXParallax);
    mRect.top = mpLevel->World2ScreenY(SUB2WORLD(mCurSubY), mYParallax);
    mRect.right = mRect.left + GetCurWidth() - 1;
    mRect.bottom = mRect.top + GetCurHeight() - 1;

    mpLevel->AddInvalidRect(&mRect);

    return result;
}

int CGameCharacter::GetRemoteAction(
    void *&Data,
    DWORD &DataSize
)
{
    return(GetNextRemoteAction(pActionQueue, Data, DataSize));
}   

int CGameCharacter::TransmitRemoteAction(
    int action, 
    void *Data, 
    DWORD DataSize
)
{
    SendRemoteAction(pObjID, action, Data, DataSize);
    return(0);
}   

void CGameCharacter::ReleaseRemoteAction(
    void    *Data
)
{
    ReleaseRemoteData(Data);
}
